iT邦幫忙

2023 iThome 鐵人賽

DAY 29
0
自我挑戰組

Go in 3o系列 第 29

[Day29] Go in 30 - 系統與檔案 - flag 與 signals

  • 分享至 

  • xImage
  •  

一、本篇題要

這個主題將了解系統與檔案,像是介紹多種讀寫純文字檔和CSV格式檔案的方式,以及檔案的存取權限。
我們會實作一個命令列應用程式,可以接收各種 flag 及其引數,方便你控制程式、顯示說明文件等,並知道如何攔截作業系統所發出的中斷訊息,並在決定要在關閉程式前做什麼處理。

  • 命令列旗標與其引數
  • 系統中斷訊號
  • 檔案存取權限

二、命令列旗標與其引數

Go語言函式庫提供許多檔案操作的功能,如開啟檔案、建立、修改檔案等等,此外程式與系統間的互動也不局限於檔案本身,我們的程式也可以接收來自使用者的命令列旗標(flag),以便指定程式要做什麼。

2.1 使用flag

就像我們在終端機使用過的一些命命工具 :

go build -o .\bin\hello_world.exe main.go

這行指令使用go build 來將 main.go 編譯成 \bin 子目錄下的 exe 執行檔,而執行檔的路徑和名稱就是透過flag -o(output)來指定

關於flag,Go語言提供 flag 套件來協助開發者。

  • 第一步,先定義自己的旗標,以下提供常用flag:
func String(name string, value string, usage string) *string

func Bool(name string, value bool, usage string) *bool

func Int(name string, value int, usage string) *int

func Int64(name string, value int64, usage string) *int64

func Float64(name string, value float64, usage string) *float64

func Duration(name string, value time.Duration, usage string) *time.Duration

//分析所有命令行參數。
func Parse()

//Args 返回所有非 flag 的命令行參數切片,而 Arg 返回給定索引的非 flag 命令行參數。
func Args() []string

func Arg(i int) string

從以上就可以看出每個函示都有以下參數:

name : flag 名稱
value : flag 預設值
usage : 說明 flag 用途(也是字串),通常在你設定flag錯誤時,這個內容就會顯示給使用者看。

舉例 :

package main 
import (
    "fmt"
    "flag"
)
func main() {
    // 定義一個旗標 -value, 接收整數, 預設值為 -1
    v := flag.Int("value", -1, "Need a value for the flag.")
    flag.Parse()
    fmt.Println(*v)
}

使用終端機指令 :
https://ithelp.ithome.com.tw/upload/images/20231007/20162693EjxgfnZpD2.png

將以上檔案編成執行檔,再用flag:
https://ithelp.ithome.com.tw/upload/images/20231007/20162693kcN9tWIUMQ.png

如果執行程式時加上 -h ,或者不確定旗標型別,程式會列出可用旗標 :
https://ithelp.ithome.com.tw/upload/images/20231007/20162693oJa8SDILdI.png

如果輸入錯誤型別 :
https://ithelp.ithome.com.tw/upload/images/20231007/20162693DEoyOFay5n.png

2.2 以旗標來決定程式的執行狀態

更複雜的例子,這次程式最多能接收三個旗標,分別是name、age、married :

有時我們會希望某些flag是執行程式必要的參數,查無此flag就要提醒使用者,
這代表我們得要謹慎決定flag的預設值,因為我們會用這個預設值,來判斷使用者是否有加上該旗標或給予正確的值。

範例 :

package main

import (
	"flag"
	"fmt"
	"os"
)

func main() {
	// 定義一個旗標 -value, 接收整數, 預設值為 -1
	n := flag.String("name", "", "your first name ?")
	i := flag.Int("age", -1, "your age ?")
	b := flag.Bool("married", false, "are you married ?")
	flag.Parse()

	if *n == "" { // 若名字旗標值為空字串,代表使用者沒有加上該旗標,或者未給值
		fmt.Println("Name is required.")
		flag.PrintDefaults() //印出所有旗標的預設值
		os.Exit(1)           //結束程式
	}

	fmt.Println("name : ", *n)
	fmt.Println("age : ", *i)
	fmt.Println("married : ", *b)
}

執行結果 :
https://ithelp.ithome.com.tw/upload/images/20231007/20162693XmZ7SFQYGs.png

如果沒有name flag :

https://ithelp.ithome.com.tw/upload/images/20231007/20162693SXIBszCROB.png

可以注意到,-married 並沒有引數,但卻仍出現 true,這表示你可以用flag.Bool()定義一個不用引數的flag,讓flag本身當作一個"開關"。若沒加上 "-married" 效果等同 "-married=false"

三、系統中斷訊號

在多數作業系統中,可以使用系統中斷訊號(signals)來對進程進行非標準的中斷,舉例來說:

當使用者在主控台按下 ctrl + c 時(^C),系統會送名為SIGINT的中斷(interrupt)訊號給程式,或者作業系統要強制終止程式,會傳送SIGTERM訊號給它,程式收到會立即結束,對於Go語言來說,就是執行 os.Exit(1)。

這樣的問題可能在於,程式中如果有使用defer延遲執行的函式,他們不會被執行。
而這些延遲執行的函式可能會負責以下:

  • 關閉資源
  • 關閉檔案
  • 結束資料庫連線

總之可能導致必要檢查無法完成。

對此,我們可以在程式中註冊這些訊號,好在收到訊號時能井然有序完成該有的善後工作,確保程式正常結束。

Go 語言的 os/signal 套件提供了對這些訊號的處理功能。

3.1 接收中斷訊號通知

如果希望程式能判斷它何時收到特定的作業系統訊號,得使用 signal 套件的 Notify() 函式來註冊 :

signal.Notify(<通知通道>, <訊號1>, <訊號2>....)

Notify 參數說明:

  1. 當註冊的訊號發生時,它會被傳入通道(channel),channel 是 Go 語言中專門用於非同步程式資料交換的管道。

  2. 接著是我們想接收到的訊號,這些都定義在 syscall 套件的常數中,如 syscall.SIGINT(中斷)、syscall.SIGTERM(終止)等。

為了能註冊和收到系統訊號,我們必須先建立一個通道和用 make() 初始化它,以便傳給 signal.Notify() 使用:

<通道> := make(chan os.Signal, 1)

chan(channel)關鍵字,代表我們要建立通道,其內容型別為 os.Signal (即系統訊號)。後面的 1 代表緩衝區(buffer)大小為1,也就是最多可以暫存 1 個訊號,若需要接收的類型比較多,可以在加大。

注意: buffer 至少必須為 1,否則程式嘗試讀取通道時很容易卡住。

建立好通道後,就可以用以下方是取一個值出來 :

<值> := <- <通道>

箭頭<- :
代表受理算符(receive operators),意義是從通道中取出一個值,接著這裡用短變數宣告'將該值附值給一個變數。

練習 : 接收中斷訊號並結束程式
以下程式模擬攔截兩個常見訊號 : syscall.SIGINT、syscall.SIGTERM。

說明: 當使用這在主控台按下CTRL + C ,作業系統會傳送SIGINT中斷訊號給應用程式。於是我們註冊這個訊號,該訊號會被加入 channel 變數。當我們知道這個訊號存在,就可以自行控制程式如何結束。

以此例來說,我們會建立一個無窮迴圈,單純離開無窮迴圈,然後讓defer可以發揮作用。

https://ithelp.ithome.com.tw/upload/images/20231007/20162693AdC9rl4MU4.png

以上練習中,展示Go語言可以攔截使用者觸發的中斷訊息或終止訊號,並讓程式正常結束,完成地必要善後工作。

關於Go的channel、非同步程序會在後續介紹,接下來將會先了解如何建立和寫入檔案~


上一篇
[Day28] Go in 30 - 處理JSON資料
下一篇
[Day30] Go in 30 - 系統與檔案 - 檔案讀取與寫入
系列文
Go in 3o30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言